﻿using BattleFury.Lib.Network.Packets;
using BattleFury.Server.Events;
using BattleFuryGameEngine;
using Lidgren.Network;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace BattleFury.Server
{
    public partial class FuryServer
    {

        private BattleFuryGame _sBFG;//our instance of the game

        #region Queues
        private ConcurrentQueue<NetIncomingMessage> _incomingEvents = new ConcurrentQueue<NetIncomingMessage>();
        private ConcurrentQueue<ServerEvent> _outgoingEvents = new ConcurrentQueue<ServerEvent>();
        private ConcurrentQueue<IPacket> _incomingPackets = new ConcurrentQueue<IPacket>();
        private ConcurrentQueue<IPacket> _outgoingPackets = new ConcurrentQueue<IPacket>();
        #endregion

        #region Threads
        private static BackgroundWorker[] _workers = new BackgroundWorker[6];
        private enum ThreadIds
        {
            Listener,
            IncomingEventsQueue,
            OutgoingEventsQueue,
            IncomingPacketsQueue,
            OutgoingPacketsQueue
        }

        #endregion

        public bool IsRunning { get; set; }

        private NetServer _server;
        private NetPeerConfiguration _config;
        private List<long> _clients = new List<long>();

        public int Port { get; set; }
        public int MaximumConnections { get; set; }
        public string AppIdentifier { get; set; }

        public event EventHandler<ConsoleMessageEventArgs> OnConsoleMessage;
        public event EventHandler<ClientConnectEventArgs> OnClientConnect;
        public event EventHandler<ClientDisconnectEventArgs> OnClientDisconnect;


        #region Initialization

        public FuryServer()
        {
            Port = 28657;
            MaximumConnections = 100;
            AppIdentifier = "Fury";
            _sBFG = BattleFuryGame.Get();
        }

        #endregion

        #region Methods

        public void Start()
        {
            _config = new NetPeerConfiguration(AppIdentifier)
            {
                MaximumConnections = MaximumConnections,
                Port = Port
            };

            _server = new NetServer(_config);

            #region Setup Listener Thread
            _workers[(int)ThreadIds.Listener] = new BackgroundWorker
            {
                WorkerReportsProgress = true
            };
            _workers[(int)ThreadIds.Listener].DoWork += ListenForIncomingNetworkEvents;
            _workers[(int)ThreadIds.Listener].ProgressChanged += ReportProgress;
            _workers[(int)ThreadIds.Listener].RunWorkerAsync();
            #endregion
            #region Setup Incoming Events Queue Thread
            _workers[(int)ThreadIds.IncomingEventsQueue] = new BackgroundWorker
            {
                WorkerReportsProgress = true
            };
            _workers[(int)ThreadIds.IncomingEventsQueue].DoWork += MonitorIncomingEventsQueue;
            //_workers[(int)ThreadIds.IncomingEventsQueue].ProgressChanged += ReportProgress;
            _workers[(int)ThreadIds.IncomingEventsQueue].RunWorkerAsync();
            #endregion
            #region Setup Incoming Packets Queue Thread
            _workers[(int)ThreadIds.IncomingPacketsQueue] = new BackgroundWorker
            {
                WorkerReportsProgress = true
            };
            _workers[(int)ThreadIds.IncomingPacketsQueue].DoWork += MonitorIncomingPacketsQueue;
            //_workers[(int)ThreadIds.IncomingPacketsQueue].ProgressChanged += ReportProgress;
            _workers[(int)ThreadIds.IncomingPacketsQueue].RunWorkerAsync();
            #endregion
            #region TODO: Setup Outgoing Events Queue Thread
            _workers[(int)ThreadIds.OutgoingEventsQueue] = new BackgroundWorker
            {
                WorkerReportsProgress = false
            };
            //_workers[(int)ThreadIds.OutgoingEventsQueue].DoWork += MonitorIncomingPacketsQueue;
            //_workers[(int)ThreadIds.IncomingPacketsQueue].ProgressChanged += ReportProgress;
            _workers[(int)ThreadIds.OutgoingEventsQueue].RunWorkerAsync();
            #endregion
            #region TODO: Setup outgoing Packets Queue Thread
            _workers[(int)ThreadIds.OutgoingPacketsQueue] = new BackgroundWorker
            {
                WorkerReportsProgress = false
            };
            _workers[(int)ThreadIds.OutgoingPacketsQueue].DoWork += MonitorOutgoingPacketQueue;
            //_workers[(int)ThreadIds.OutgoingPacketsQueue].ProgressChanged += ReportProgress;
            _workers[(int)ThreadIds.OutgoingPacketsQueue].RunWorkerAsync();
            #endregion
            try
            {
                IsRunning = true;

                var msg = string.Format("Starting {0} server", AppIdentifier);
                SendConsoleMessage(msg);

                _server.Start();

                msg = string.Format("Started on port {0}", Port);
                SendConsoleMessage(msg);
            }
            catch (Exception e)
            {
                SendConsoleMessage(e.Message);
                IsRunning = false;
                _server.Shutdown(e.Message);
            }

            _sBFG.Start();
        }

        private void MonitorOutgoingPacketQueue(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker _worker = (BackgroundWorker)sender;
            if (_worker.WorkerReportsProgress)
                _worker.ReportProgress(100);

            IPacket msg;
            while (IsRunning)
            {
                while (_outgoingPackets.TryDequeue(out msg))
                {
                    foreach (var c in _clients)
                    {
                    }
                }
            }
        }



        private void MonitorIncomingEventsQueue(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker _worker = (BackgroundWorker)sender;
            _worker.ReportProgress(100);

            NetIncomingMessage msg;
            while (IsRunning)
            {
                while (_incomingEvents.TryDequeue(out msg))
                {
                    switch (msg.MessageType)
                    {
                        case NetIncomingMessageType.DebugMessage:
                        case NetIncomingMessageType.ErrorMessage:
                        case NetIncomingMessageType.WarningMessage:
                        case NetIncomingMessageType.VerboseDebugMessage:
                            var text = msg.ReadString();
                            SendConsoleMessage(text);
                            break;
                        case NetIncomingMessageType.StatusChanged:
                            var status = (NetConnectionStatus)msg.ReadByte();
                            var reason = msg.ReadString();
                            SendConsoleMessage(NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);
                            if (status == NetConnectionStatus.Connected)
                            {
                                SendConsoleMessage("Remote hail: " + msg.SenderConnection.RemoteHailMessage.ReadString());
                                if (OnClientConnect != null)
                                    OnClientConnect(this, new ClientConnectEventArgs());
                            }
                            if (status == NetConnectionStatus.Connected)
                            {
                                var serverEvent = new ServerEventClientConnect(DateTime.Now)
                                {
                                    Data = msg.SenderConnection.RemoteUniqueIdentifier
                                };
                                //TODO:  not sure what to do with this yet
                                _clients.Add((long)serverEvent.Data);
                                _sBFG.AddPlayer((long)serverEvent.Data);
                                //_incomingEvents.Enqueue(serverEvent);
                            }
                            if (status == NetConnectionStatus.Disconnected)
                            {
                                var serverEvent = new ServerEventClientDisconnect(DateTime.Now)
                                {
                                    Data = msg.SenderConnection.RemoteUniqueIdentifier
                                };
                                //TODO:  Delete Client from list
                                //for now, just remove all clients wit the same ID
                                _clients.RemoveAll(c => c == (long)serverEvent.Data);
                                _sBFG.RemovePlayer((long)serverEvent.Data);
                                //_incomingEvents.Enqueue(serverEvent);
                                if (OnClientDisconnect != null)
                                    OnClientDisconnect(this, new ClientDisconnectEventArgs());
                            }
                            // UpdateConnectionsList();
                            break;
                        case NetIncomingMessageType.Data:
                            IPacket p = PacketTools.ReadPacket(msg);
                            _incomingPackets.Enqueue(p);
                            break;
                        default:
                            SendConsoleMessage("Unhandled type: " + msg.MessageType + " " + msg.LengthBytes + " bytes " + msg.DeliveryMethod + "|" + msg.SequenceChannel);
                            break;
                    }
                    //  _worker.ReportProgress(100 - _incomingEvents.Count > 100 ? 100 : _incomingEvents.Count);
                }
            }
        }



        public void Shutdown()
        {
            var msg = string.Format("Shutting down {0} server", AppIdentifier);
            _server.Shutdown(msg);
            SendConsoleMessage(msg);
        }

        private void ReportProgress(object sender, ProgressChangedEventArgs progressChangedEventArgs)
        {
            SendConsoleMessage(sender.GetType().ToString() + "\t" + progressChangedEventArgs.ProgressPercentage);
        }

        private void ListenForIncomingNetworkEvents(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker _worker = (BackgroundWorker)sender;

            NetIncomingMessage msg;
            while (IsRunning)
            {
                while ((msg = _server.ReadMessage()) != null)
                {
                    //_worker.ReportProgress(0);

                    _incomingEvents.Enqueue(msg);
                    Thread.Sleep(1);
                }
                Thread.Sleep(1);
            }
        }
        #endregion

        private void SendConsoleMessage(string message)
        {
            if (OnConsoleMessage == null) return;
            var args = new ConsoleMessageEventArgs
            {
                Message = message
            };
            OnConsoleMessage(this, args);
        }



        public void SendServerMessage(string message)
        {
            var all = _server.Connections;
            var om = _server.CreateMessage();
            om.Write(message);
            _server.SendMessage(om, all, NetDeliveryMethod.ReliableOrdered, 0);
        }

    }
}